<?php


/** ensure this file is being included by a parent file */
defined('_VALID_MOS') or die('Direct Access to this location is not allowed.'); 

/**
* @package HelpDesk
*/
class HelpDeskObject 
{
    var $_isError;
    var $_lastError;


	/**
	 * A hack to support __construct() on PHP 4
	 *
	 * Hint: descendant classes have no PHP4 class_name() constructors,
	 * so this constructor gets called first and calls the top-layer __construct()
	 * which (if present) should call parent::__construct()
	 *
	 * @access	public
	 * @return	Object
	 * @since	1.5
	 */
	function HelpDeskObject()
	{
		$args = func_get_args();
		call_user_func_array( array(&$this, '__construct'), $args);
	}

	/**
	 * Class constructor, overridden in descendant classes.
	 *
	 * @access	protected
	 * @since	1.5
	 */
	function __construct() {}
    
	/**
	 * 
	 * @param $prop
	 * @param $default
	 * @return unknown_type
	 */
    function get($prop, $default=null) {
        if (isset($this->$prop)) return $this->$prop;
        return $default;
    }
    
    /**
     * 
     * @param $prop
     * @param $value
     * @return unknown_type
     */
    function set($prop, $value=null)
    {
		$previous = isset($this->$property) ? $this->$property : null;
		$this->$property = $value;
		return $previous;
    }
    
    /**
     * 
     * @return unknown_type
     */
    function isError()
    {
        return $this->_isError;
    }
    
    /**
     * 
     * @return unknown_type
     */
    function getLastError()
    {
        return $this->_lastError;
    }
    
    /**
     * 
     * @param $errorMessage
     * @param $errorCode
     * @return unknown_type
     */
    function throwError($errorMessage, $errorCode=null)
    {
        $this->_isError = true;
        $this->_lastError = new HelpDeskError(
            $errorMessage, 
            $errorCode
        );
    }

    /**
     * 
     * @param $errorMessage
     * @param $errorCode
     * @return unknown_type
     */
    function setError($errorMessage, $errorCode=null )
    {
        $this->_isError = true;
        $this->_lastError = new HelpDeskError( $errorCode, $errorMessage );
    }	

    /**
     * 
     * @return unknown_type
     */
    function getError()
    {
        return $this->_lastError->message;
    }

	/**
	 * Retrieves the Table
	 * @return array Array of objects 
	 */	
	function getTable( $name ) 
	{
		$row = HelpDeskTable::getInstance( $name );	
		return $row;
	}

	/**
	 * Returns an associative array of object properties
	 *
	 * @access	public
	 * @param	boolean $public If true, returns only the public properties
	 * @return	array
 	 */
	function getProperties( $public = true ) {
		$vars  = get_object_vars($this);

        if($public) {
			foreach ($vars as $key => $value) {
				if ('_' == substr($key, 0, 1)) {
					unset($vars[$key]);
				}
			}
		}

        return $vars;
	}
	
}

/**
* @package HelpDesk
*/
class HelpDeskError extends HelpDeskObject
{
    var $code;
    var $message;
    
    /**
     * 
     * @param $message
     * @param $code
     * @return unknown_type
     */
    function __construct($code, $message=null) 
    {
        unset($this->_isError);
        unset($this->_lastError);
        $this->code    = $code;
        $this->message = $message;
        return $this;
    }
    
    /**
     * 
     * @param $message
     * @param $code
     * @return unknown_type
     */
    function HelpDeskError($code, $message=null)
    {
        $this->__construct($code, $message);
    }
    
    /**
     * 
     * @param $level
     * @param $code
     * @param $msg
     * @param $info
     * @param $backtrace
     * @return unknown_type
     */
	function & raise($level, $code, $msg, $info = null, $backtrace = false)
	{
		// don't need a full-fledged error handler
		echo "<h3>ERROR</h3>";
		echo "<p>Level: {$level}</p>";
		echo "<p>Code: {$code}</p>";
		echo "<p>Message: {$msg}</p>";
		 
		return;
	}

	/**
	 * Wrapper method for the {@link raise()} method with predefined error level of E_ERROR and backtrace set to true.
	 *
	 * @static
	 * @param	string	$code	The application-internal error code for this error
	 * @param	string	$msg	The error message, which may also be shown the user if need be.
	 * @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	 * @return	object	$error	The configured HelpDeskError object
	 * @since	1.5
	 */
	function & raiseError($code, $msg, $info = null)
	{
		$reference = & HelpDeskError::raise(E_ERROR, $code, $msg, $info, true);
		return $reference;
	}

	/**
	 * Wrapper method for the {@link raise()} method with predefined error level of E_WARNING and backtrace set to false.
	 *
	 * @static
	 * @param	string	$code	The application-internal error code for this error
	 * @param	string	$msg	The error message, which may also be shown the user if need be.
	 * @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	 * @return	object	$error	The configured HelpDeskError object
	 * @since	1.5
	 */
	function & raiseWarning($code, $msg, $info = null)
	{
		$reference = & HelpDeskError::raise(E_WARNING, $code, $msg, $info);
		return $reference;
	}

	/**
	 * Wrapper method for the {@link raise()} method with predefined error level of E_NOTICE and backtrace set to false.
	 *
	 * @static
	 * @param	string	$code	The application-internal error code for this error
	 * @param	string	$msg	The error message, which may also be shown the user if need be.
	 * @param	mixed	$info	Optional: Additional error information (usually only developer-relevant information that the user should never see, like a database DSN).
	 * @return	object	$error	The configured HelpDeskError object
	 * @since	1.5
	 */
	function & raiseNotice($code, $msg, $info = null)
	{
		$reference = & HelpDeskError::raise(E_NOTICE, $code, $msg, $info);
		return $reference;
	}
	
}

/**
 * 
 * 
 *
 */
class HelpDeskControllerBase extends HelpDeskObject
{
	/**
	 * 
	 * @param $config
	 * @return unknown_type
	 */
	function __construct( $config = array() )
	{
		//Initialize private variables
		$this->_redirect	= null;
		$this->_message		= null;
		$this->_messageType = 'message';
		$this->_taskMap		= array();
		$this->_methods		= array();
		$this->_data		= array();

		// Get the methods only for the final controller class
		$thisMethods	= get_class_methods( get_class( $this ) );
		$baseMethods	= get_class_methods( 'HelpDeskController' );
		$methods		= array_diff( $thisMethods, $baseMethods );

		// Add default display method
		$methods[] = 'display';

		// Iterate through methods and map tasks
		foreach ( $methods as $method )
		{
			if ( substr( $method, 0, 1 ) != '_' ) {
				$this->_methods[] = strtolower( $method );
				// auto register public methods as tasks
				$this->_taskMap[strtolower( $method )] = $method;
			}
		}
		
		// If the default task is set, register it as such
		if ( array_key_exists( 'default_task', $config ) ) {
			$this->registerDefaultTask( $config['default_task'] );
		} else {
			$this->registerDefaultTask( 'display' );
		}
		
		// set the view name
		if (empty( $this->_name ))
		{
			if (array_key_exists('name', $config))  {
				$this->_name = $config['name'];
			} else {
				$this->_name = $this->getName();
			}
		}
	}

	/**
	 * Method to get the controller name
	 *
	 * The dispatcher name by default parsed using the classname, or it can be set
	 * by passing a $config['name'] in the class constructor
	 *
	 * @access	public
	 * @return	string The name of the dispatcher
	 * @since	1.5
	 */
	function getName()
	{

		if (empty( $this->_name ))
		{
			$r = null;
			if ( !preg_match( '/(.*)Controller/i', get_class( $this ), $r ) ) {
				// HelpDeskError::raiseError(500, "JController::getName() : Cannot get or parse class name.");
				echo HelpDeskText::_( "Could not determine class name" );
			}
			$this->_name = strtolower( $r[1] );
		}

		return $this->_name;
	}
	
	/**
	 * Register (map) a task to a method in the class.
	 *
	 * @access	public
	 * @param	string	The task.
	 * @param	string	The name of the method in the derived class to perform
	 *                  for this task.
	 * @return	void
	 * @since	1.5
	 */
	function registerTask( $task, $method )
	{
		if ( in_array( strtolower( $method ), $this->_methods ) ) {
			$this->_taskMap[strtolower( $task )] = $method;
		}
	}
	
	/**
	 * 
	 * @param $task
	 * @return unknown_type
	 */
	function execute( $task )
	{
		$retval = null;
		if (method_exists( $this, $task )) { 
			$retval = $this->$task();
			return $retval;
		}
		
		$task = strtolower( $task );
		if (isset( $this->_taskMap[$task] )) {
			$doTask = $this->_taskMap[$task];
		} elseif (isset( $this->_taskMap['__default'] )) {
			$doTask = $this->_taskMap['__default'];
		} elseif (method_exists( $this, $task )) { 
			$doTask = $task; 
		} else {
			echo HelpDeskText::_( 'Task ['.$task.'] not found' );
			return;
		}

		// Record the actual task being fired
		$this->_doTask = $doTask;
		
		// Make sure we have task
		if (method_exists( $this, $doTask ))
		{
			$retval = $this->$doTask();
			return $retval;
		}
		else
		{
			echo HelpDeskText::_( 'Task ['.$task.'] not found' );
			return;
		}

	}
	
	/**
	 * Set a URL for browser redirection.
	 *
	 * @access	public
	 * @param	string URL to redirect to.
	 * @param	string	Message to display on redirect. Optional, defaults to
	 * 			value set internally by controller, if any.
	 * @param	string	Message type. Optional, defaults to 'message'.
	 * @return	void
	 * @since	1.5
	 */
	function setRedirect( $url, $msg = null, $type = 'message' )
	{
		$this->_redirect = $url;
		if ($msg !== null) {
			// controller may have set this directly
			$this->_message	= $msg;
		}
		$this->_messageType	= $type;
	}
	
	/**
	 * Redirects the browser or returns false if no redirect is set.
	 *
	 * @access	public
	 * @return	boolean	False if no redirect exists.
	 * @since	1.5
	 */
	function redirect()
	{
		if (isset($this->_redirect)) 
		{
			$mainframe = HelpDeskConfig::getApplication();
			$mainframe->redirect( $this->_redirect, $this->_message, $this->_messageType );
		}
		return false;
	}

	/**
	 * Method to get a model object, loading it if required.
	 *
	 * @access	public
	 * @param	string	The model name. Optional.
	 * @param	string	The class prefix. Optional.
	 * @param	array	Configuration array for model. Optional.
	 * @return	object	The model.
	 * @since	1.5
	 */
	function &getModel( $name = '', $prefix = '', $config = array() )
	{
		if ( empty( $name ) ) {
			$name = $this->getName();
		}

		if ( empty( $prefix ) ) {
			$prefix = $this->getName() . 'Model';
		}
		
		if ( $model = & $this->_createModel( $name, $prefix, $config ) )
		{
			// could do things
		}
		return $model;
	}
	
	/**
	 * 
	 * @param $name
	 * @param $type
	 * @param $prefix
	 * @param $config
	 * @return unknown_type
	 */
	function &getView( $name = '', $type = '', $prefix = '', $config = array() )
	{
		static $views;

		if ( !isset( $views ) ) {
			$views = array();
		}

		if ( empty( $name ) ) {
			$name = $this->getName();
		}

		if ( empty( $prefix ) ) {
			$prefix = $this->getName() . 'View';
		}

		if ( empty( $views[$name] ) )
		{
			if ( $view = & $this->_createView( $name, $prefix, $type, $config ) ) {
				$views[$name] = & $view;
			} else {
				echo HelpDeskText::_( 'View not found [name, type, prefix]:' ) 
					. ' ' . $name . ',' . $type . ',' . $prefix ;
				$return = array();
				return $return;
			}
		}

		return $views[$name];
	}
	
	/**
	 * 
	 * @param $cachable
	 * @return unknown_type
	 */
	function display($cachable=false)
	{

		$viewType	= "html"; // TODO Make this support pdf override
		$viewName	= HelpDeskRequest::getVar( 'view', $this->getName() );
		$viewLayout	= HelpDeskRequest::getVar( 'layout', 'default' );

		$view = & $this->getView( $viewName, $viewType );

		// Get/Create the model
		if ($model = & $this->getModel($viewName)) {
			// Push the model into the view (as default)
			$view->setModel($model, true);
		}

		// Set the layout
		$view->setLayout($viewLayout);

		// Display the view
		$view->display();
	}
	
	/**
	 * Register the default task to perform if a mapping is not found.
	 *
	 * @access	public
	 * @param	string The name of the method in the derived class to perform if
	 * a named task is not found.
	 * @return	void
	 * @since	1.5
	 */
	function registerDefaultTask( $method )
	{
		$this->registerTask( '__default', $method );
	}
	
	/**
	 * Method to load and return a model object.
	 *
	 * @access	private
	 * @param	string  The name of the model.
	 * @param	string	Optional model prefix.
	 * @param	array	Configuration array for the model. Optional.
	 * @return	mixed	Model object on success; otherwise null
	 * failure.
	 * @since	1.5
	 */
	function &_createModel( $name, $prefix = '', $config = array() )
	{
		$result = null;

		// Clean the model name
		$modelName	 = preg_replace( '/[^A-Z0-9_]/i', '', $name );
		$classPrefix = preg_replace( '/[^A-Z0-9_]/i', '', $prefix );

		$result = HelpDeskModel::getInstance($modelName, $classPrefix, $config);
		return $result;
	}
	
	/**
	 * 
	 * @param $name
	 * @param $prefix
	 * @param $type
	 * @param $config
	 * @return unknown_type
	 */
	function &_createView( $name, $prefix = '', $type = '', $config = array() )
	{
		$result = null;

		// Clean the view name
		$viewName	 = preg_replace( '/[^A-Z0-9_]/i', '', $name );
		$classPrefix = preg_replace( '/[^A-Z0-9_]/i', '', $prefix );
		$viewType	 = preg_replace( '/[^A-Z0-9_]/i', '', $type );

		// Build the view class name
		$viewClass = $classPrefix . $viewName;

		if ( !class_exists( $viewClass ) )
		{
			$path = $this->_createFileName( 'view', array( 'name' => $viewName, 'type' => $viewType) );
			include_once( $path );
			if ( !class_exists( $viewClass ) )
			{			
				echo HelpDeskText::_( 'View class not found [class, file]:' )
				. ' ' . $viewClass . ', ' . $path ;
				$result = array();
				return $result;
			}
		}

		$result = new $viewClass($config);
		return $result;
	}
	
	/**
	 * 
	 * @param $type
	 * @param $parts
	 * @return unknown_type
	 */
	function _createFileName($type, $parts = array())
	{
		$filename = '';
		$mainframe 	= HelpDeskConfig::getApplication();
		$path = $mainframe->isAdmin() ? JPATH_COMPONENT_ADMINISTRATOR : JPATH_COMPONENT_SITE;
		
		switch ( $type )
		{
			case 'view':
				if ( !empty( $parts['type'] ) ) {
					$parts['type'] = '.'.$parts['type'];
				}
				$filename = 'view'.$parts['type'].'.php';
				// TODO Include a switch based on isAdmin vs isSite
				$filename = $path.DS."views".DS.strtolower($parts['name']).DS.$filename;
			break;
		}
		return $filename;		
	}
	
}

class HelpDeskView extends HelpDeskObject
{
	/**
	 * 
	 * @param $config
	 * @return unknown_type
	 */
	function __construct( $config = array() )
	{	
		//set the view name
		if (empty( $this->_name ))
		{
			$this->_name = $this->getName();
		}
		
		// set the layout
		if (array_key_exists('layout', $config)) {
			$this->setLayout($config['layout']);
		} else {
			$this->setLayout('default');
		}
	}
	
	
	
	/**
	 * 
	 * @param $tpl
	 * @return unknown_type
	 */
	function display($tpl = null)
	{
		$result = $this->loadTemplate($tpl);
		echo $result;
	}
	
	/**
	 * 
	 * @param $key
	 * @param $val
	 * @return unknown_type
	 */
	function assignRef($key, &$val)
	{
		if (is_string($key) && substr($key, 0, 1) != '_')
		{
			$this->$key =& $val;
			return true;
		}

		return false;
	}
	
	/**
	 * 
	 * @param $tpl
	 * @return unknown_type
	 */
	function loadTemplate( $tpl = null)
	{
		global $mainframe, $option;

		// clear prior output
		$this->_output = null;

		//create the template file name based on the layout
		$file = isset($tpl) ? $this->_layout.'_'.$tpl : $this->_layout;
		// clean the file name
		$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $file);
		$tpl  = preg_replace('/[^A-Z0-9_\.-]/i', '', $tpl);

		
		// load the template script
		$this->_template = $this->_createFileName( 'template' );

		if ($this->_template != false)
		{
			// unset so as not to introduce into template scope
			unset($tpl);
			unset($file);

			// never allow a 'this' property
			if (isset($this->this)) {
				unset($this->this);
			}

			// start capturing output into a buffer
			ob_start();
			// include the requested template filename in the local scope
			// (this will execute the view logic).
			include_once( $this->_template );

			// done with the requested template; get the buffer and
			// clear it.
			$this->_output = ob_get_contents();
			ob_end_clean();

			return $this->_output;
		}
		else {
			echo HelpDeskText::_( 'Layout "' . $file . '" not found' );
		}
	}
	
	/**
	 * 
	 * @return unknown_type
	 */
	function getName()
	{
		if (empty( $this->_name ))
		{
			$r = null;
			if (!preg_match('/View((view)*(.*(view)?.*))$/i', get_class($this), $r)) {
				echo HelpDeskText::_( "HelpDeskView::getName() : Could not determine class name" );
			}
			$this->_name = strtolower( $r[3] );
		}

		return $this->_name;
	}
	
	/**
	 * 
	 * @param $model
	 * @param $default
	 * @return unknown_type
	 */
	function &setModel( &$model, $default = false )
	{
		$name = strtolower($model->getName());
		$this->_models[$name] = &$model;

		if ($default) {
			$this->_defaultModel = $name;
		}
		return $model;
	}
	
	/**
	 * 
	 * @param $name
	 * @return unknown_type
	 */
	function &getModel( $name = null )
	{
		if ($name === null) {
			$name = $this->_defaultModel;
		}
		return $this->_models[strtolower( $name )];
	}
	
	/**
	 * 
	 * @param $layout
	 * @return unknown_type
	 */
	function setLayout($layout)
	{
		$previous = isset( $this->_layout ) ? $this->_layout : 'default';
		$this->_layout = $layout;
		return $previous;
	}
	
	/**
	 * 
	 * @return unknown_type
	 */
	function getLayout()
	{
		return $this->_layout;
	}
	
	/**
	 * 
	 * @param $type
	 * @param $parts
	 * @return unknown_type
	 */
	function _createFileName($type, $parts = array())
	{
		$mainframe 	= HelpDeskConfig::getApplication();
		$path = $mainframe->isAdmin() ? JPATH_COMPONENT_ADMINISTRATOR : JPATH_COMPONENT_SITE;
		
		switch (strtolower($type))
		{
			case "layout":
			default:
				$filename = $path.DS."views".DS."{$this->_name}".DS."tmpl".DS.$this->getLayout().".php";
			  break;
		}
		
		return $filename;			
	}
		
}

/**
* @package HelpDesk
*/
class HelpDeskModel extends HelpDeskObject 
{
	var $_id 	= null;
	
	/**
	 * 
	 * @return unknown_type
	 */
	function __construct()
	{
		$array = HelpDeskRequest::getVar('cid', '0', '', 'array');
		$this->setId( (int) $array[0] );
	}
	
	/**
	 * 
	 * @param $type
	 * @param $prefix
	 * @param $config
	 * @return unknown_type
	 */
	function getInstance( $type, $prefix = '', $config = array() )
	{
		$type		= preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
		$modelClass	= $prefix.ucfirst($type);
		$result		= false;

		if (!class_exists( $modelClass ))
		{
			// try to include the paths for it
			$path = HelpDeskModel::_createFileName( 'model', array( 'name' => $type) );
			include_once( $path );
		}

		$result = new $modelClass($config);
		return $result;
	}
	
	/**
	 * 
	 * @return unknown_type
	 */
	function getName()
	{
		if (empty( $this->_name ))
		{
			$r = null;
			if (!preg_match('/Model(.*)/i', get_class($this), $r)) {
				echo HelpDeskText::_( "HelpDeskModel::getName() : Could not determine class name" );
			}
			$this->_name = strtolower( $r[1] );
		}

		return $this->_name;
	}

	/**
	 * 
	 * @param $type
	 * @param $parts
	 * @return unknown_type
	 */
	function _createFileName($type, $parts = array())
	{
		$filename = '';
		$mainframe 	= HelpDeskConfig::getApplication();
		$path = $mainframe->isAdmin() ? JPATH_COMPONENT_ADMINISTRATOR : JPATH_COMPONENT_SITE;
		
		switch($type)
		{
			case 'model':
				$filename = $path.DS."models".DS.strtolower($parts['name']).".php";
				break;

		}
		return $filename;		
	}
			
	/**
	 * Method to set the identifier
	 *
	 * @access	public
	 * @param	int identifier
	 * @return	void
	 */
	function setId($id) {
		// Set id and wipe data
		$this->_id		= $id;
		$this->_data	= null;
	}

	/**
	 * 
	 * @return unknown_type
	 */
	function getId()
	{
		$array = HelpDeskRequest::getVar('cid', '0', '', 'array');
		$this->setId( (int) $array[0] );
		return $this->_id;
	}
		
	/**
	 * Retrieves the data
	 * @return array Array of objects containing the data
	 */
	function getData() 
	{
		$database = HelpDeskConfig::getDBO();
		// load the data if it doesn't already exist
		if (empty( $this->_data )) 
		{
			$query = $this->_buildQuery();
			$database->setQuery( $query, $this->getLimitStart(), $this->getLimit() );
			$this->_data = $database->loadObjectList();
		}
		return $this->_data;
	}
	
	/**
	 * Retrieves the limit
	 * @return array Array of objects 
	 */
	function getLimit( $site='administrator' ) {
		global $mainframe, $option;
		if (empty($this->_limit)) {
			if ($site == "site") {
				$limit = intval( mosGetParam( $_GET, 'limit', mosGetParam( $_POST, 'limit', mosGetParam( $_SESSION[_THISCOMPONENT], 'limit', $mainframe->getCfg('list_limit') ) ) ) );
			} else {
				$limit	= $mainframe->getUserStateFromRequest( 'global.list.limit', 'limit', $mainframe->getCfg('list_limit'), 'int' );
			}
			$_SESSION[_THISCOMPONENT]['limit'] = $limit;
			$this->_limit = $limit;
		}

		return $this->_limit;
	}

	/**
	 * Retrieves the limitstart
	 * @return array Array of objects 
	 */
	function getLimitStart( $default="0", $site='administrator' ) {
		global $mainframe, $option;
		if (empty($this->_limitstart)) {
			if ($site == "site") {
				$limitstart = intval( mosGetParam( $_GET, 'limitstart', mosGetParam( $_POST, 'limitstart', $default ) ) );
			} else {
				$limitstart	= $mainframe->getUserStateFromRequest( strtolower('HelpDesk.limitstart'), 'limitstart', $default, 'int' );
			}
			$limitstart = ($this->getLimit( $site ) != 0 ? (floor($limitstart / $this->getLimit( $site )) * $this->getLimit( $site )) : 0);
			$this->_limitstart = $limitstart;
		}

		return $this->_limitstart;
	}

	/**
	 * Retrieves the order
	 * @return array Array of objects 
	 */
	function getOrder( $default=null, $site='administrator' ) {
		global $mainframe, $option;
		if (empty($this->_order)) {
			if ($site == "site") {
				$order = strval( mosGetParam( $_GET, 'order', mosGetParam( $_POST, 'order', mosGetParam( $_SESSION[_THISCOMPONENT], 'order', $default ) ) ) );
			} else {
				$order = strval( htmlspecialchars( $mainframe->getUserStateFromRequest( "order{$option}", 'order', $default ) ) );	
			}
			$_SESSION[_THISCOMPONENT]['order'] = $order;
			$this->_order = $order;
		}

		return $this->_order;
	}
	
	/**
	 * Retrieves the order dir
	 * @return array Array of objects 
	 */
	function getOrderDir( $default="asc", $site='administrator' ) {
		global $mainframe, $option;
		if (empty($this->_order_dir)) {
			if ($site == "site") {
				$order_dir = strval( mosGetParam( $_GET, 'order_dir', mosGetParam( $_POST, 'order_dir', mosGetParam( $_SESSION[_THISCOMPONENT], 'order_dir', $default ) ) ) );
			} else {
				$order_dir = strval( htmlspecialchars( $mainframe->getUserStateFromRequest( "order_dir{$option}", 'order_dir', $default ) ) );	
			}
			$_SESSION[_THISCOMPONENT]['order_dir'] = $order_dir;
			$this->_order_dir = $order_dir;
		}

		return $this->_order_dir;
	}	
	
	/**
	 * Retrieves the search
	 * @return array Array of objects 
	 */
	function getSearch( $option, $default=null, $site='administrator' ) {
		global $mainframe;
		if (empty($this->_search)) {
			if ($site == "site") {
				$search = strval( mosGetParam( $_GET, 'search', mosGetParam( $_POST, 'search', mosGetParam( $_SESSION[_THISCOMPONENT], 'search', $default ) ) ) );
			} else {
				$search = $mainframe->getUserStateFromRequest( "search{$option}", 'search', $default );
			}
			
			if (get_magic_quotes_gpc()) {
				$search	= stripslashes( $search );
			}
			$_SESSION[_THISCOMPONENT]['search'] = $search;
			$this->_search 		= $search;
		}

		return $this->_search;
	}
	
	/**
	 * 
	 * @param $statename
	 * @param $type
	 * @param $default
	 * @param $site
	 * @return unknown_type
	 */
	function getState( $statename='', $type='string', $default=null, $site='administrator' )
	{
		$state = $this->getStateVar( $statename, $statename, $type, $default, $site );
		return $state;
	}
	
	/**
	 * Retrieves a var
	 * @return array Array of objects 
	 */
	function getStateVar( $varname, $statename='', $type='string', $default=null, $site='administrator' ) 
	{
		global $mainframe, $option;
		if (empty($this->$statename)) {
			if ($site == "site") {
				$value = strval( mosGetParam( $_GET, $varname, mosGetParam( $_POST, $varname, mosGetParam( $_SESSION[_THISCOMPONENT], $varname, $default ) ) ) );
			} else {
				$value	= strval( htmlspecialchars( $mainframe->getUserStateFromRequest( $varname, $varname, $default ) ) );
			}			
			
			switch($type) {
				case "int":
				case "integer":
					$value = intval( $value );
				  break;
				case "str":
				case "string":
				default:
					$value = strval( $value );			
				  break;
			}
			$_SESSION[_THISCOMPONENT][$varname] = $value;
			$this->$statename = $value;
		}
		
		return $this->$statename;
	}
	
	/**
	 * Retrieves the value
	 * @return mixed
	 */
	function getField( $fieldname='fieldname', $default='', $type='string', $site='administrator' ) 
	{
		$state = $this->getStateVar( $fieldname, $fieldname, $type, $default, $site );
		return $state;		
	}
	
	/**
	 * Retrieves the pagination
	 * @return array Array of objects 
	 */
	function getPagination( $site='administrator' ) {
		global $mainframe, $database, $mosConfig_absolute_path;
		if (empty($this->_pagination)) {

		// get all data		
			$query = $this->_buildQuery();
			$database->setQuery( $query );
			$allrecords = $database->loadObjectList();
			$total = count($allrecords);
			
		// pagination
			require_once( $mosConfig_absolute_path.'/administrator/includes/pageNavigation.php' );
			$pageNav = new mosPageNav( $total, $this->getLimitStart( 0, $site ), $this->getLimit( $site )  );
			$this->_pagination 		= $pageNav;
		}

		return $this->_pagination;
	}
	
}

class HelpDeskTable extends mosDBTable
{
	var		$_errors		= array();
	
	/**
	 * 
	 * @return unknown_type
	 */
	function __construct( $table, $key, &$db )
	{
		$this->mosDBTable( $table, $key, $db );
	}
	
	/**
	 * Add an error message
	 *
	 * @param	string $error Error message
	 * @access	public
	 * @since	1.0
	 */
	function setError($error)
	{
		array_push($this->_errors, $error);
	}
	
	/**
	 * 
	 * @param $property
	 * @param $default
	 * @return unknown_type
	 */
	function get($property, $default=null)
	{
		if(isset($this->$property)) {
			return $this->$property;
		}
		return $default;
	}

	/**
	 * Returns an associative array of object properties
	 *
	 * @access	public
	 * @param	boolean $public If true, returns only the public properties
	 * @return	array
	 * @see		get()
	 * @since	1.5
 	 */
	function getProperties( $public = true )
	{
		$vars  = get_object_vars($this);

        if($public)
		{
			foreach ($vars as $key => $value)
			{
				if ('_' == substr($key, 0, 1)) {
					unset($vars[$key]);
				}
			}
		}

        return $vars;
	}
	
	/**
	 * 
	 * @param $path
	 * @return unknown_type
	 */
	function addIncludePath( $path="" )
	{
		return true;
	}
	
	/**
	 * 
	 * @param $type
	 * @param $prefix
	 * @return unknown_type
	 */
	function getInstance( $type='', $prefix='Table' )
	{
		$filetype = strtolower( $type );
		require_once(JPATH_COMPONENT_ADMINISTRATOR.DS."tables".DS."{$filetype}.php");
		
		$database = HelpDeskConfig::getDBO();
		$classname = $prefix.$type;
		$return = new $classname( $database );	
		return $return;
	}
	
	/**
	 * Loads a row from the database and binds the fields to the object properties
	 *
	 * @access	public
	 * @param	mixed	Optional primary key.  If not specifed, the value of current key is used
	 * @return	boolean	True if successful
	 */
	function load( $oid=null )
	{
		$k = $this->_tbl_key;

		if ($oid !== null) {
			$this->$k = $oid;
		}

		$oid = $this->$k;

		if ($oid === null) {
			return false;
		}
		$this->reset();

		$db =& $this->getDBO();

		$query = 'SELECT *'
		. ' FROM '.$this->_tbl
		. ' WHERE '.$this->_tbl_key.' = '.$db->Quote($oid);
		$db->setQuery( $query );

		if ($result = $db->loadAssoc()) {
			return $this->bind($result);
		}
		else
		{
			$this->setError( $db->getErrorMsg() );
			return false;
		}
	}
	
	function bind( $from, $ignore=array() )
	{
		$fromArray	= is_array( $from );
		$fromObject	= is_object( $from );

		if (!$fromArray && !$fromObject)
		{
			$this->setError( get_class( $this ).'::bind failed. Invalid from argument' );
			return false;
		}
		if (!is_array( $ignore )) {
			$ignore = explode( ' ', $ignore );
		}
		foreach ($this->getProperties() as $k => $v)
		{
			// internal attributes of an object are ignored
			if (!in_array( $k, $ignore ))
			{
				if ($fromArray && isset( $from[$k] )) {
					$this->$k = $from[$k];
				} else if ($fromObject && isset( $from->$k )) {
					$this->$k = $from->$k;
				}
			}
		}
		return true;
	}
	
	/**
	 * Get the internal database object
	 *
	 * @return object A JDatabase based object
	 */
	function &getDBO()
	{
		return $this->_db;
	}

	/**
	 * Set the internal database object
	 *
	 * @param	object	$db	A JDatabase based object
	 * @return	void
	 */
	function setDBO(&$db)
	{
		$this->_db =& $db;
	}
	
}

class HelpDeskRequest
{
	/**
	 * Fetches and returns a given variable.
	 *
	 * The default behaviour is fetching variables depending on the
	 * current request method: GET and HEAD will result in returning
	 * an entry from $_GET, POST and PUT will result in returning an
	 * entry from $_POST.
	 *
	 * You can force the source by setting the $hash parameter:
	 *
	 *   post		$_POST
	 *   get		$_GET
	 *   files		$_FILES
	 *   cookie		$_COOKIE
	 *   env		$_ENV
	 *   server		$_SERVER
	 *   method		via current $_SERVER['REQUEST_METHOD']
	 *   default	$_REQUEST
	 *
	 * @static
	 * @param	string	$name		Variable name
	 * @param	string	$default	Default value if the variable does not exist
	 * @param	string	$hash		Where the var should come from (POST, GET, FILES, COOKIE, METHOD)
	 * @param	string	$type		Return type for the variable, for valid values see {@link HelpDeskFilterInput::clean()}
	 * @param	int		$mask		Filter mask for the variable
	 * @return	mixed	Requested variable
	 * @since	1.5
	 */
	function getVar($name, $default = null, $hash = 'default', $type = 'none', $mask = 0)
	{
		// Ensure hash and type are uppercase
		$hash = strtoupper( $hash );
		if ($hash === 'METHOD') {
			$hash = strtoupper( $_SERVER['REQUEST_METHOD'] );
		}
		$type	= strtoupper( $type );
		$sig	= $hash.$type.$mask;

		// Get the input hash
		switch ($hash)
		{
			case 'GET' :
				$input = &$_GET;
				break;
			case 'POST' :
				$input = &$_POST;
				break;
			case 'FILES' :
				$input = &$_FILES;
				break;
			case 'COOKIE' :
				$input = &$_COOKIE;
				break;
			case 'ENV'    :
				$input = &$_ENV;
				break;
			case 'SERVER'    :
				$input = &$_SERVER;
				break;
			default:
				$input = &$_REQUEST;
				$hash = 'REQUEST';
				break;
		}

		if (isset($GLOBALS['_JREQUEST'][$name]['SET.'.$hash]) && ($GLOBALS['_JREQUEST'][$name]['SET.'.$hash] === true)) {
			// Get the variable from the input hash
			$var = (isset($input[$name]) && $input[$name] !== null) ? $input[$name] : $default;
			$var = HelpDeskRequest::_cleanVar($var, $mask, $type);
		}
		elseif (!isset($GLOBALS['_JREQUEST'][$name][$sig]))
		{
			if (isset($input[$name]) && $input[$name] !== null) {
				// Get the variable from the input hash and clean it
				$var = HelpDeskRequest::_cleanVar($input[$name], $mask, $type);

				// Handle magic quotes compatability
				if (get_magic_quotes_gpc() && ($var != $default) && ($hash != 'FILES')) {
					$var = HelpDeskRequest::_stripSlashesRecursive( $var );
				}

				$GLOBALS['_JREQUEST'][$name][$sig] = $var;
			}
			elseif ($default !== null) {
				// Clean the default value
				$var = HelpDeskRequest::_cleanVar($default, $mask, $type);
			}
			else {
				$var = $default;
			}
		} else {
			$var = $GLOBALS['_JREQUEST'][$name][$sig];
		}

		return $var;
	}

	/**
	 * 
	 * @param $name
	 * @param $value
	 * @param $hash
	 * @param $overwrite
	 * @return unknown_type
	 */
	function setVar($name, $value = null, $hash = 'method', $overwrite = true)
	{
		//If overwrite is true, makes sure the variable hasn't been set yet
		if(!$overwrite && array_key_exists($name, $_REQUEST)) {
			return $_REQUEST[$name];
		}

		// Clean global request var
		$GLOBALS['_JREQUEST'][$name] = array();

		// Get the request hash value
		$hash = strtoupper($hash);
		if ($hash === 'METHOD') {
			$hash = strtoupper($_SERVER['REQUEST_METHOD']);
		}

		$previous	= array_key_exists($name, $_REQUEST) ? $_REQUEST[$name] : null;

		switch ($hash)
		{
			case 'GET' :
				$_GET[$name] = $value;
				$_REQUEST[$name] = $value;
				break;
			case 'POST' :
				$_POST[$name] = $value;
				$_REQUEST[$name] = $value;
				break;
			case 'COOKIE' :
				$_COOKIE[$name] = $value;
				$_REQUEST[$name] = $value;
				break;
			case 'FILES' :
				$_FILES[$name] = $value;
				break;
			case 'ENV'    :
				$_ENV['name'] = $value;
				break;
			case 'SERVER'    :
				$_SERVER['name'] = $value;
				break;
		}

		// Mark this variable as 'SET'
		$GLOBALS['_JREQUEST'][$name]['SET.'.$hash] = true;
		$GLOBALS['_JREQUEST'][$name]['SET.REQUEST'] = true;

		return $previous;
	}
	
	/**
	 * Clean up an input variable.
	 *
	 * @param mixed The input variable.
	 * @param int Filter bit mask. 1=no trim: If this flag is cleared and the
	 * input is a string, the string will have leading and trailing whitespace
	 * trimmed. 2=allow_raw: If set, no more filtering is performed, higher bits
	 * are ignored. 4=allow_html: HTML is allowed, but passed through a safe
	 * HTML filter first. If set, no more filtering is performed. If no bits
	 * other than the 1 bit is set, a strict filter is applied.
	 * @param string The variable type {@see HelpDeskFilterInput::clean()}.
	 */
	function _cleanVar($var, $mask = 0, $type=null)
	{
		// Static input filters for specific settings
		static $noHtmlFilter	= null;
		static $safeHtmlFilter	= null;

		// If the no trim flag is not set, trim the variable
		if (!($mask & 1) && is_string($var)) {
			$var = trim($var);
		}

		// Now we handle input filtering
		if ($mask & 2)
		{
			// If the allow raw flag is set, do not modify the variable
			$var = $var;
		}
		elseif ($mask & 4)
		{
			// If the allow html flag is set, apply a safe html filter to the variable
			if (is_null($safeHtmlFilter)) {
				$safeHtmlFilter = & HelpDeskFilterInput::getInstance(null, null, 1, 1);
			}
			$var = $safeHtmlFilter->clean($var, $type);
		}
		else
		{
			// Since no allow flags were set, we will apply the most strict filter to the variable
			if (is_null($noHtmlFilter)) {
				$noHtmlFilter = & HelpDeskFilterInput::getInstance(/* $tags, $attr, $tag_method, $attr_method, $xss_auto */);
			}
			$var = $noHtmlFilter->clean($var, $type);
		}
		return $var;
	}

	/**
	 * Strips slashes recursively on an array
	 *
	 * @access	protected
	 * @param	array	$array		Array of (nested arrays of) strings
	 * @return	array	The input array with stripshlashes applied to it
	 */
	function _stripSlashesRecursive( $value )
	{
		$value = is_array( $value ) ? array_map( array( 'HelpDeskRequest', '_stripSlashesRecursive' ), $value ) : stripslashes( $value );
		return $value;
	}
	
	/**
	 * Checks for a form token in the request
	 *
	 * Use in conjuction with josSpoofValue()
	 *
	 * @param	string	The request method in which to look for the token key
	 * @return	boolean	True if found and valid, false otherwise
	 */
	function checkToken( $method='post', $redirect=null )
	{
		$token	= josSpoofValue();
		if(!HelpDeskRequest::getVar( $token, '', $method, 'alnum' )) 
		{
			if ($redirect)
			{
				$mainframe = HelpDeskConfig::getApplication();
				$return = HelpDeskRoute::_('index.php');
				$mainframe->redirect($return, HelpDeskText::_('Invalid Token'));				
			} else 
			{
				return false;	
			}
		} else 
		{
			return true;
		}
	}
}

class HelpDeskFilterInput extends HelpDeskObject
{
	var $tagsArray; // default = empty array
	var $attrArray; // default = empty array

	var $tagsMethod; // default = 0
	var $attrMethod; // default = 0

	var $xssAuto; // default = 1
	var $tagBlacklist = array ('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 'script', 'style', 'title', 'xml');
	var $attrBlacklist = array ('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers

	/**
	 * Constructor for inputFilter class. Only first parameter is required.
	 *
	 * @access	protected
	 * @param	array	$tagsArray	list of user-defined tags
	 * @param	array	$attrArray	list of user-defined attributes
	 * @param	int		$tagsMethod	WhiteList method = 0, BlackList method = 1
	 * @param	int		$attrMethod	WhiteList method = 0, BlackList method = 1
	 * @param	int		$xssAuto	Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
	 * @since	1.5
	 */
	function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
	{
		// Make sure user defined arrays are in lowercase
		$tagsArray = array_map('strtolower', (array) $tagsArray);
		$attrArray = array_map('strtolower', (array) $attrArray);

		// Assign member variables
		$this->tagsArray	= $tagsArray;
		$this->attrArray	= $attrArray;
		$this->tagsMethod	= $tagsMethod;
		$this->attrMethod	= $attrMethod;
		$this->xssAuto		= $xssAuto;
	}

	/**
	 * Returns a reference to an input filter object, only creating it if it doesn't already exist.
	 *
	 * This method must be invoked as:
	 * 		<pre>  $filter = & HelpDeskFilterInput::getInstance();</pre>
	 *
	 * @static
	 * @param	array	$tagsArray	list of user-defined tags
	 * @param	array	$attrArray	list of user-defined attributes
	 * @param	int		$tagsMethod	WhiteList method = 0, BlackList method = 1
	 * @param	int		$attrMethod	WhiteList method = 0, BlackList method = 1
	 * @param	int		$xssAuto	Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
	 * @return	object	The HelpDeskFilterInput object.
	 * @since	1.5
	 */
	function & getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
	{
		static $instances;

		$sig = md5(serialize(array($tagsArray,$attrArray,$tagsMethod,$attrMethod,$xssAuto)));

		if (!isset ($instances)) {
			$instances = array();
		}

		if (empty ($instances[$sig])) {
			$instances[$sig] = new HelpDeskFilterInput($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto);
		}

		return $instances[$sig];
	}

	/**
	 * Method to be called by another php script. Processes for XSS and
	 * specified bad code.
	 *
	 * @access	public
	 * @param	mixed	$source	Input string/array-of-string to be 'cleaned'
	 * @param	string	$type	Return type for the variable (INT, FLOAT, BOOLEAN, WORD, ALNUM, CMD, BASE64, STRING, ARRAY, PATH, NONE)
	 * @return	mixed	'Cleaned' version of input parameter
	 * @since	1.5
	 * @static
	 */
	function clean($source, $type='string')
	{
		// Handle the type constraint
		switch (strtoupper($type))
		{
			case 'INT' :
			case 'INTEGER' :
				// Only use the first integer value
				preg_match('/-?[0-9]+/', (string) $source, $matches);
				$result = @ (int) $matches[0];
				break;

			case 'FLOAT' :
			case 'DOUBLE' :
				// Only use the first floating point value
				preg_match('/-?[0-9]+(\.[0-9]+)?/', (string) $source, $matches);
				$result = @ (float) $matches[0];
				break;

			case 'BOOL' :
			case 'BOOLEAN' :
				$result = (bool) $source;
				break;

			case 'WORD' :
				$result = (string) preg_replace( '/[^A-Z_]/i', '', $source );
				break;

			case 'ALNUM' :
				$result = (string) preg_replace( '/[^A-Z0-9]/i', '', $source );
				break;

			case 'CMD' :
				$result = (string) preg_replace( '/[^A-Z0-9_\.-]/i', '', $source );
				$result = ltrim($result, '.');
				break;

			case 'BASE64' :
				$result = (string) preg_replace( '/[^A-Z0-9\/+=]/i', '', $source );
				break;

			case 'STRING' :
				// Check for static usage and assign $filter the proper variable
				if(isset($this) && is_a( $this, 'HelpDeskFilterInput' )) {
					$filter =& $this;
				} else {
					$filter =& HelpDeskFilterInput::getInstance();
				}
				$result = (string) $filter->_remove($filter->_decode((string) $source));
				break;

			case 'ARRAY' :
				$result = (array) $source;
				break;

			case 'PATH' :
				$pattern = '/^[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';
				preg_match($pattern, (string) $source, $matches);
				$result = @ (string) $matches[0];
				break;

			case 'USERNAME' :
				$result = (string) preg_replace( '/[\x00-\x1F\x7F<>"\'%&]/', '', $source );
				break;

			default :
				// Check for static usage and assign $filter the proper variable
				if(is_object($this) && get_class($this) == 'HelpDeskFilterInput') {
					$filter =& $this;
				} else {
					$filter =& HelpDeskFilterInput::getInstance();
				}
				// Are we dealing with an array?
				if (is_array($source)) {
					foreach ($source as $key => $value)
					{
						// filter element for XSS and other 'bad' code etc.
						if (is_string($value)) {
							$source[$key] = $filter->_remove($filter->_decode($value));
						}
					}
					$result = $source;
				} else {
					// Or a string?
					if (is_string($source) && !empty ($source)) {
						// filter source for XSS and other 'bad' code etc.
						$result = $filter->_remove($filter->_decode($source));
					} else {
						// Not an array or string.. return the passed parameter
						$result = $source;
					}
				}
				break;
		}
		return $result;
	}

	/**
	 * Function to determine if contents of an attribute is safe
	 *
	 * @static
	 * @param	array	$attrSubSet	A 2 element array for attributes name,value
	 * @return	boolean True if bad code is detected
	 * @since	1.5
	 */
	function checkAttribute($attrSubSet)
	{
		$attrSubSet[0] = strtolower($attrSubSet[0]);
		$attrSubSet[1] = strtolower($attrSubSet[1]);
		return (((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') || (strpos($attrSubSet[1], 'javascript:') !== false) || (strpos($attrSubSet[1], 'behaviour:') !== false) || (strpos($attrSubSet[1], 'vbscript:') !== false) || (strpos($attrSubSet[1], 'mocha:') !== false) || (strpos($attrSubSet[1], 'livescript:') !== false));
	}

	/**
	 * Internal method to iteratively remove all unwanted tags and attributes
	 *
	 * @access	protected
	 * @param	string	$source	Input string to be 'cleaned'
	 * @return	string	'Cleaned' version of input parameter
	 * @since	1.5
	 */
	function _remove($source)
	{
		$loopCounter = 0;

		// Iteration provides nested tag protection
		while ($source != $this->_cleanTags($source))
		{
			$source = $this->_cleanTags($source);
			$loopCounter ++;
		}
		return $source;
	}

	/**
	 * Internal method to strip a string of certain tags
	 *
	 * @access	protected
	 * @param	string	$source	Input string to be 'cleaned'
	 * @return	string	'Cleaned' version of input parameter
	 * @since	1.5
	 */
	function _cleanTags($source)
	{
		/*
		 * In the beginning we don't really have a tag, so everything is
		 * postTag
		 */
		$preTag		= null;
		$postTag	= $source;
		$currentSpace = false;
		$attr = '';	 // moffats: setting to null due to issues in migration system - undefined variable errors

		// Is there a tag? If so it will certainly start with a '<'
		$tagOpen_start	= strpos($source, '<');

		while ($tagOpen_start !== false)
		{
			// Get some information about the tag we are processing
			$preTag			.= substr($postTag, 0, $tagOpen_start);
			$postTag		= substr($postTag, $tagOpen_start);
			$fromTagOpen	= substr($postTag, 1);
			$tagOpen_end	= strpos($fromTagOpen, '>');

			// Let's catch any non-terminated tags and skip over them
			if ($tagOpen_end === false) {
				$postTag		= substr($postTag, $tagOpen_start +1);
				$tagOpen_start	= strpos($postTag, '<');
				continue;
			}

			// Do we have a nested tag?
			$tagOpen_nested = strpos($fromTagOpen, '<');
			$tagOpen_nested_end	= strpos(substr($postTag, $tagOpen_end), '>');
			if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) {
				$preTag			.= substr($postTag, 0, ($tagOpen_nested +1));
				$postTag		= substr($postTag, ($tagOpen_nested +1));
				$tagOpen_start	= strpos($postTag, '<');
				continue;
			}

			// Lets get some information about our tag and setup attribute pairs
			$tagOpen_nested	= (strpos($fromTagOpen, '<') + $tagOpen_start +1);
			$currentTag		= substr($fromTagOpen, 0, $tagOpen_end);
			$tagLength		= strlen($currentTag);
			$tagLeft		= $currentTag;
			$attrSet		= array ();
			$currentSpace	= strpos($tagLeft, ' ');

			// Are we an open tag or a close tag?
			if (substr($currentTag, 0, 1) == '/') {
				// Close Tag
				$isCloseTag		= true;
				list ($tagName)	= explode(' ', $currentTag);
				$tagName		= substr($tagName, 1);
			} else {
				// Open Tag
				$isCloseTag		= false;
				list ($tagName)	= explode(' ', $currentTag);
			}

			/*
			 * Exclude all "non-regular" tagnames
			 * OR no tagname
			 * OR remove if xssauto is on and tag is blacklisted
			 */
			if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) {
				$postTag		= substr($postTag, ($tagLength +2));
				$tagOpen_start	= strpos($postTag, '<');
				// Strip tag
				continue;
			}

			/*
			 * Time to grab any attributes from the tag... need this section in
			 * case attributes have spaces in the values.
			 */
			while ($currentSpace !== false)
			{
				$attr			= '';
				$fromSpace		= substr($tagLeft, ($currentSpace +1));
				$nextSpace		= strpos($fromSpace, ' ');
				$openQuotes		= strpos($fromSpace, '"');
				$closeQuotes	= strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1;

				// Do we have an attribute to process? [check for equal sign]
				if (strpos($fromSpace, '=') !== false) {
					/*
					 * If the attribute value is wrapped in quotes we need to
					 * grab the substring from the closing quote, otherwise grab
					 * till the next space
					 */
					if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false)) {
						$attr = substr($fromSpace, 0, ($closeQuotes +1));
					} else {
						$attr = substr($fromSpace, 0, $nextSpace);
					}
				} else {
					/*
					 * No more equal signs so add any extra text in the tag into
					 * the attribute array [eg. checked]
					 */
					if ($fromSpace != '/') {
						$attr = substr($fromSpace, 0, $nextSpace);
					}
				}

				// Last Attribute Pair
				if (!$attr && $fromSpace != '/') {
					$attr = $fromSpace;
				}

				// Add attribute pair to the attribute array
				$attrSet[] = $attr;

				// Move search point and continue iteration
				$tagLeft		= substr($fromSpace, strlen($attr));
				$currentSpace	= strpos($tagLeft, ' ');
			}

			// Is our tag in the user input array?
			$tagFound = in_array(strtolower($tagName), $this->tagsArray);

			// If the tag is allowed lets append it to the output string
			if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) {

				// Reconstruct tag with allowed attributes
				if (!$isCloseTag) {
					// Open or Single tag
					$attrSet = $this->_cleanAttributes($attrSet);
					$preTag .= '<'.$tagName;
					for ($i = 0; $i < count($attrSet); $i ++)
					{
						$preTag .= ' '.$attrSet[$i];
					}

					// Reformat single tags to XHTML
					if (strpos($fromTagOpen, '</'.$tagName)) {
						$preTag .= '>';
					} else {
						$preTag .= ' />';
					}
				} else {
					// Closing Tag
					$preTag .= '</'.$tagName.'>';
				}
			}

			// Find next tag's start and continue iteration
			$postTag		= substr($postTag, ($tagLength +2));
			$tagOpen_start	= strpos($postTag, '<');
		}

		// Append any code after the end of tags and return
		if ($postTag != '<') {
			$preTag .= $postTag;
		}
		return $preTag;
	}

	/**
	 * Internal method to strip a tag of certain attributes
	 *
	 * @access	protected
	 * @param	array	$attrSet	Array of attribute pairs to filter
	 * @return	array	Filtered array of attribute pairs
	 * @since	1.5
	 */
	function _cleanAttributes($attrSet)
	{
		// Initialize variables
		$newSet = array();

		// Iterate through attribute pairs
		for ($i = 0; $i < count($attrSet); $i ++)
		{
			// Skip blank spaces
			if (!$attrSet[$i]) {
				continue;
			}

			// Split into name/value pairs
			$attrSubSet = explode('=', trim($attrSet[$i]), 2);
			list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]);

			/*
			 * Remove all "non-regular" attribute names
			 * AND blacklisted attributes
			 */
			if ((!preg_match('/[a-z]*$/i', $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on')))) {
				continue;
			}

			// XSS attribute value filtering
			if ($attrSubSet[1]) {
				// strips unicode, hex, etc
				$attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
				// strip normal newline within attr value
				$attrSubSet[1] = preg_replace('/[\n\r]/', '', $attrSubSet[1]);
				// strip double quotes
				$attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
				// convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
				if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'")) {
					$attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
				}
				// strip slashes
				$attrSubSet[1] = stripslashes($attrSubSet[1]);
			}

			// Autostrip script tags
			if (HelpDeskFilterInput::checkAttribute($attrSubSet)) {
				continue;
			}

			// Is our attribute in the user input array?
			$attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);

			// If the tag is allowed lets keep it
			if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) {

				// Does the attribute have a value?
				if ($attrSubSet[1]) {
					$newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"';
				} elseif ($attrSubSet[1] == "0") {
					/*
					 * Special Case
					 * Is the value 0?
					 */
					$newSet[] = $attrSubSet[0].'="0"';
				} else {
					$newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"';
				}
			}
		}
		return $newSet;
	}

	/**
	 * Try to convert to plaintext
	 *
	 * @access	protected
	 * @param	string	$source
	 * @return	string	Plaintext string
	 * @since	1.5
	 */
	function _decode($source)
	{
		// entity decode
		$trans_tbl = get_html_translation_table(HTML_ENTITIES);
		foreach($trans_tbl as $k => $v) {
			$ttr[$v] = utf8_encode($k);
		}
		$source = strtr($source, $ttr);
		// convert decimal
		$source = preg_replace('/&#(\d+);/me', "utf8_encode(chr(\\1))", $source); // decimal notation
		// convert hex
		$source = preg_replace('/&#x([a-f0-9]+);/mei', "utf8_encode(chr(0x\\1))", $source); // hex notation
		return $source;
	}
	
	/**
	* Replaces &amp; with & for xhtml compliance
	*
	* @todo There must be a better way???
	*
	* @static
	* @since 1.5
	*/
	function ampReplace( $text )
	{
		$text = str_replace( '&&', '*--*', $text );
		$text = str_replace( '&#', '*-*', $text );
		$text = str_replace( '&amp;', '&', $text );
		$text = preg_replace( '|&(?![\w]+;)|', '&amp;', $text );
		$text = str_replace( '*-*', '&#', $text );
		$text = str_replace( '*--*', '&&', $text );

		return $text;
	}
}

class HelpDeskApplication extends mosMainFrame
{
	
	/**
	 * 
	 * @return unknown_type
	 */
public static function getInstance( $refresh=false ) 
	{
		static $instance;
		
		if (!is_object($instance) || $refresh) 
		{
			$db = HelpDeskConfig::getDBO();
			$option = HelpDeskRequest::getVar( 'option' );
			global $mainframe;
			$isAdmin = $mainframe->isAdmin();
			$basePath = $isAdmin ? '..' : '.';
			// new mosMainFrame( $database, $option, '..', true );
			// mosMainFrame( &$db, $option, $basePath, $isAdmin=false )			
			$instance = new HelpDeskApplication( $db, $option, $basePath, $isAdmin );
		}

		return $instance;
	}
	
	/**
	 * 
	 * @return unknown_type
	 */
	function isSite()
	{
		if ($this->isAdmin())
		{
			return false;
		}
		return true;
	}
	
	/**
	 * 
	 * @param $url
	 * @param $msg
	 * @param $type
	 * @return unknown_type
	 */
	function redirect( $url='', $msg='', $type='' )
	{
		mosRedirect( $url, $msg );
	}
}

class HelpDeskRoute extends HelpDeskObject
{
	/**
	 * 
	 * @param $url
	 * @param $makeSafe
	 * @return unknown_type
	 */
	function _( $url, $makeSafe=false )
	{
		$return = $url;
		
		// if no Itemid, add it
		$findme = "Itemid";
		$pos1 = stripos($url, $findme);
		if (!$pos1)
		{
			$url = HelpDeskUrl::addItemid( $url );
		}
		
		$mainframe = HelpDeskConfig::getApplication();
		if (!$mainframe->isAdmin())
		{
			$return = sefRelToAbs( $url );	
		}
		
		return $return;		
	}
}